using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEngine;

namespace Mirror.Tests.Generators
{
    public struct FloatStringStruct
    {
        public float value;
        public string anotherValue;
    }

    public class CollectionWriterGenerator : GeneratorBase
    {
        [MenuItem("Tools/Generate Test files/Collection Writer")]
        public static void GenerateTestFiles()
        {
            List<string> classes = new List<string>();
            foreach (string collectionType in collectionTypes)
            {
                foreach (string elementType in elementTypes)
                {
                    IEnumerable<string> dataValues = valuesForType(elementType);
                    classes.Add(Classes(elementType, collectionType, dataValues));
                }
            }
            string main = Main(classes);
            Save(main, "CollectionWriterTests", true);
        }


        static readonly string[] elementTypes = new string[]
        {
           "int",
           "string",
           nameof(Vector3),
           nameof(FloatStringStruct),
           nameof(ClassWithNoConstructor)
        };
        static IEnumerable<string> valuesForType(string elementType)
        {
            switch (elementType)
            {
                case "int":
                    return new string[] { "3", "4", "5" };
                case "string":
                    return new string[] { "\"Some\"", "\"String\"", "\"Value\"" };
                case nameof(Vector3):
                    return new string[] {
                        "new Vector3(1, 2, 3)",
                        "new Vector3(4, 5, 6)",
                        "new Vector3(7, 8, 9)"
                    };
                case nameof(FloatStringStruct):
                    return new string[] {
                        "new FloatStringStruct { value = 3, anotherValue = \"Some\" }",
                        "new FloatStringStruct { value = 4, anotherValue = \"String\" }",
                        "new FloatStringStruct { value = 5, anotherValue = \"Values\" }",
                    };
                case nameof(ClassWithNoConstructor):
                    return new string[] {
                        "new ClassWithNoConstructor { a = 3 }",
                        "new ClassWithNoConstructor { a = 4 }",
                        "new ClassWithNoConstructor { a = 5 }",
                    };
                default:
                    throw new System.ArgumentException($"No Values for {elementType}");
            }
        }
        const string ArrayType = "Array";
        const string ListType = "List";
        const string ArraySegmentType = "ArraySegment";

        static readonly string[] collectionTypes = new string[]
        {
            ArrayType,
            ArraySegmentType,
            ListType,
        };

        const string NameSpace = BaseNameSpace + ".CollectionWriters";

        static string Main(IEnumerable<string> classes)
        {
            string mergedClasses = Merge(classes);
            return $@"// Generated by {nameof(CollectionWriterGenerator)}.cs
using System;
using System.Collections.Generic;
using Mirror.Tests.Generators;
using NUnit.Framework;
using UnityEngine;

namespace {NameSpace}
{{
    {mergedClasses}
}}";
        }

        static string MessageField(string elementType, string collectionType)
        {
            switch (collectionType)
            {
                case ArrayType:
                    return $"{elementType}[]";
                case ListType:
                    return $"List<{elementType}>";
                case ArraySegmentType:
                    return $"ArraySegment<{elementType}>";
                default:
                    throw new System.ArgumentException($"Collection must be array, list or segment but was {collectionType}");
            }
        }


        static string Classes(string elementType, string collectionType, IEnumerable<string> dataValues)
        {
            string messageField = MessageField(elementType, collectionType);
            string[] values = dataValues.ToArray();

            return $@"
    public class {collectionType}_{elementType}_Test
    {{
        public class Message : NetworkMessage
        {{
            public {messageField} collection;
        }}

        [Test]
        public void SendsNull()
        {{
            Message message = new Message
            {{
                collection = default
            }};

            byte[] data = MessagePacker.Pack(message);

            Message unpacked = MessagePacker.Unpack<Message>(data);
            {messageField} unpackedCollection = unpacked.collection;

            Assert.That({CheckCollection(collectionType)}, Is.Null.Or.Empty);
        }}

        [Test]
        public void SendsEmpty()
        {{
            {NewEmptyCollection(elementType, collectionType)}

            byte[] data = MessagePacker.Pack(message);

            Message unpacked = MessagePacker.Unpack<Message>(data);
            {messageField} unpackedCollection = unpacked.collection;

            Assert.IsNotNull({CheckCollection(collectionType)});
            Assert.IsEmpty({CheckCollection(collectionType)});
        }}

        [Test]
        public void SendsData()
        {{
            {NewCollection(elementType, collectionType, dataValues)}

            byte[] data = MessagePacker.Pack(message);

            Message unpacked = MessagePacker.Unpack<Message>(data);
            {messageField} unpackedCollection = unpacked.collection;

            Assert.IsNotNull({CheckCollection(collectionType)});
            Assert.IsNotEmpty({CheckCollection(collectionType)});
            Assert.That({ElementEqualCheck(elementType, ReadValue(collectionType, 0), values[0])});
            Assert.That({ElementEqualCheck(elementType, ReadValue(collectionType, 1), values[1])});
            Assert.That({ElementEqualCheck(elementType, ReadValue(collectionType, 2), values[2])});
        }}
    }}";
        }

        static string ElementEqualCheck(string elementType, string collectionValue, string expected)
        {
            switch (elementType)
            {
                case nameof(ClassWithNoConstructor):
                    return $"{collectionValue}.a, Is.EqualTo({expected}.a)";
                default:
                    return $"{collectionValue}, Is.EqualTo({expected})";
            }
        }

        static string CheckCollection(string collectionType)
        {
            switch (collectionType)
            {
                case ArraySegmentType:
                    return $"unpackedCollection.Array";
                default:
                    return $"unpackedCollection";
            }
        }

        static string ReadValue(string collectionType, int index)
        {
            switch (collectionType)
            {
                case ArraySegmentType:
                    return $"unpackedCollection.Array[unpackedCollection.Offset + {index}]";
                default:
                    return $"unpackedCollection[{index}]";
            }
        }


        static string NewEmptyCollection(string elementType, string collectionType)
        {
            switch (collectionType)
            {
                case ArraySegmentType:
                    return NewEmptyArragSegment(elementType, collectionType);
                default:
                    return NewEmptyCollectionDefault(elementType, collectionType);
            }
        }

        static string NewEmptyCollectionDefault(string elementType, string collectionType)
        {
            string messageField = MessageField(elementType, collectionType);

            return $@"Message = new Message
            {{
                collection = new {messageField} {{}}
            }};";
        }

        static string NewEmptyArragSegment(string elementType, string collectionType)
        {
            string messageField = MessageField(elementType, collectionType);

            return $@"{elementType}[] array = new {elementType}[]
            {{
                default,
                default,
                default,
            }};

            Message message = new Message
            {{
                collection = new {messageField}(array, 0, 0)
            }};";
        }

        static string NewCollection(string elementType, string collectionType, IEnumerable<string> dataValues)
        {
            switch (collectionType)
            {
                case ArraySegmentType:
                    return NewArragSegment(elementType, collectionType, dataValues);
                default:
                    return NewCollectionDefault(elementType, collectionType, dataValues);
            }
        }

        static string NewCollectionDefault(string elementType, string collectionType, IEnumerable<string> dataValues)
        {
            string mergedValues = Merge(dataValues, ", ");
            string messageField = MessageField(elementType, collectionType);

            return $@"Message = new Message
            {{
                collection = new {messageField}
                {{
                    {mergedValues}
                }}
            }};";
        }

        static string NewArragSegment(string elementType, string collectionType, IEnumerable<string> dataValues)
        {
            return $@"{elementType}[] array = new {elementType}[]
            {{
                default,
                {Merge(dataValues, ", ")},
                default,
                default,
                default,
            }};


            Message message = new Message
            {{
                collection = new ArraySegment<{elementType}>(array, 1, 3)
            }};";
        }
    }
}
